来写一个属于自己的Web服务器
脚本之家
你与百万开发者在一起
1
前言
什么是Web,维基百科的描述如下:
万维网(英语:World Wide Web),亦作“WWW”、“Web”,是一个透过互联网访问的,由许多互相链接的超文本组成的系统。
—— 维基百科
我们的生活现在已经离不开互联网,我们日常使用的互联网服务中,其中的相当的一部分是Web系统所提供的,我们每天使用的搜索,新闻,购物等服务,其背后的实现,也是基于Web的技术栈,但是,Web系统究竟是怎么运作的呢?接下来我们就一起来实现一个最简单的Web服务器(使用python3),我们这里说的Web服务器是指HTTP服务器。
2
socket
socket是一种操作系统提供的进程间通信机制,简单来讲,socket是用来通信用的,它实现了TCP与UDP协议,Web系统是基于HTTP协议的,所以这里,我们使用socket来实现一个简单的Web服务器。
3
使用Python来实现一个最简单的HTTP服务器
1、新建一个文件夹WebServer,并在该文件下新建一个文件server.py
2、打开该文件,写入以下代码:
# 导入socket包
import socket
# 服务器IP与端口
HttpPort = 18080
HttpHost = ('localhost', HttpPort)
# 头部信息
HttpResponseHeader = '''HTTP/1.1 200 OK
Content-Type: text/html;charset=UTF-8
'''
# Header与Body分隔符
LineSeparator = '
'# HTTP响应
HttpResponseBody = ''
# 创建基于IPV4 TCP的socket
ServerSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# socket的绑定与监听
ServerSocket.bind(HttpHost)
ServerSocket.listen(100)
print("server is running")
# 处理客户端连接
while True:
HttpResponseBody = '很开心遇见你 :)'
Client, Address = ServerSocket.accept()
Request = Client.recv(1024).decode(encoding='utf-8')
Client.sendall((HttpResponseHeader + HttpResponseBody).encode(encoding='utf-8'))
Client.close()
3、我们在该目录下打开shell,并输入以下命令:
python3 server.py
我们可以看到以下输出:
我们打开浏览器,并在地址栏输入http://127.0.0.1:18080,可以看到以下输出:
上面的这一段代码的效果,我们已经看到了,但是,这段代码究竟做了什么呢?
TCP是传输层的协议,HTTP是应用层的协议,HTTP协议基于TCP协议,我们通过socket,建立起了客户端到服务器的TCP连接,服务器先进行监听,客户端发起一个连接,服务器收到该连接请求之后并同意建立连接,之后客户端向服务端发起HTTP请求,我们的服务端对该HTTP请求进行一定的解析之后,发送响应给客户端,然后关闭连接,这就完成了一次HTTP请求。
4
实现解析URL参数与用户提交的表单
1、我们更新server.py的代码如下:
# 导入socket包
import socket
# 服务器IP与端口
HttpPort = 18080
HttpHost = ('localhost', HttpPort)
# 头部信息
HttpResponseHeader = '''HTTP/1.1 200 OK
Content-Type: text/html;charset=UTF-8
'''
# Header与Body分隔符
LineSeparator = '
'# HTTP响应
HttpResponseBody = ''
# 创建基于IPV4 TCP的socket
ServerSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# socket的绑定与监听
ServerSocket.bind(HttpHost)
ServerSocket.listen(100)
print("server is running at http://localhost:%d" % HttpPort)
# 获取HTTP请求报头
def get_headers(request):
# 分隔符切割
headers_array = request.split('
')# HTTP请求头首行 是请求方法 请求路径 HTTP协议版本
headers = {}
for header_item in headers_array[1:]:
item_ = header_item.split(': ')
headers[item_[0]] = item_[1]
return headers
# 获取URL参数
def get_get_args(request_url):
get_args_arr = request_url[request_url.find('?')+1:].split('&')
# print(get_args_arr)
get_args = {}
try:
for get_item in get_args_arr:
item_ = get_item.split('=')
get_args[item_[0]] = item_[1]
return get_args
except:
return {}
# 获取POST表单参数
def get_post_args(request_body):
post_args_arr = request_body.split('&')
post_args = {}
for post_item in post_args_arr:
item_ = post_item.split('=')
post_args[item_[0]] = item_[1]
return post_args
# 处理客户端连接
while True:
Client, Address = ServerSocket.accept()
# 客户端的HTTP请求
Request = Client.recv(1024).decode(encoding='utf-8')
# 将请求分为报头与主体
RequestText = Request.split(LineSeparator)
# 报头
RequestHeader = RequestText[0]
# 主体
RequestBody = RequestText[1]
# 请求方法
RequestMethod = RequestHeader.split(' ')[0]
# 请求路径
RequestUrl = RequestHeader.split(' ')[1]
# 请求头部
RequestHeaders = get_headers(RequestHeader)
# 响应清空
HttpResponseBody = ''
# GET请求处理
if RequestMethod == 'GET':
HttpResponseBody += '''<html>
Your method is GET and your request url is ''' + RequestUrl + '''
<br>
The following are you headers :<br>
'''
for item in RequestHeaders.items():
HttpResponseBody += ('key :' + item[0] + ",value :" + item[1] + '<br>')
HttpResponseBody += 'There are your url args :<br>'
URL_args = get_get_args(RequestUrl)
for item in URL_args.items():
HttpResponseBody += ('key :' + item[0] + ",value :" + item[1] + '<br>')
HttpResponseBody += '<br>The next session is post test:<br>'
HttpResponseBody += '''
<form action="/" method="post">
<p>Text1: <input type="text" name="Text1" /></p>
<p>Text2: <input type="text" name="Text2" /></p>
<input type="submit" value="Submit" />
</form>
'''
HttpResponseBody += '</html>'
# POST方法的处理
elif RequestMethod == 'POST':
HttpResponseBody += '''<html>
Your method is POST and your request url is ''' + RequestUrl + '''
<br>
The following are you headers :<br>
'''
for item in RequestHeaders.items():
HttpResponseBody += ('key :' + item[0] + ",value :" + item[1] + '<br>')
HttpResponseBody += 'This is your form :<br>'
PostArgs = get_post_args(RequestBody)
for item in PostArgs.items():
HttpResponseBody += ('key :' + item[0] + ",value :" + item[1] + '<br>')
HttpResponseBody += '''
<br>Try to get test <br>
<a href="http://''' + RequestHeaders['Host'] + '''/">get test</a>
</html>
'''
# 暂未支持的请求方法
else:
HttpResponseBody += '''
<html> So sorry
''' + RequestMethod + '''
method is not support :( <br>
</html>
'''
Client.sendall((HttpResponseHeader + HttpResponseBody).encode(encoding='utf-8'))
Client.close()
2、然后执行: python3 server.py
3、我们可以看到能够如下显示:
GET请求:
POST请求:
这里我们主要是对参数进行了解析,然后并对其进行了显示,现实中,我们使用搜索引擎的时候,关键字一般都是通过GET方法来加上参数,需要用户提交用户名密码或者其他表单时,我们一般使用POST方法,有关更多的HTTP资料,请参考这里MDN 。
5
实现显示图片
1、我们在WebServer目录下新建一个名为index.html文件,并写入以下内容:
<html>
<body>
<a href="/pages/">pages</a>
</body>
</html>
2、然后在WebServer目录下新建一个文件夹pages,并在该目录下新建一个名为index.html的文件,并写入以下内容:
<html>
I like this pic:<br>
<img src="pic.jpg"/>
</html>
3、在pages目录下保存一个jpg格式的图像文件,并将其命名为pic.jpg
4、这一步,我们更新server.py文件为以下内容:
# 导入socket包
import socket
# 服务器IP与端口
HttpPort = 18080
HttpHost = ('localhost', HttpPort)
# 头部信息
HttpHtmlResponseHeader = '''HTTP/1.1 200 OK
Content-Type: text/html;charset=UTF-8
'''
HttpImageResponseHeader = '''HTTP/1.1 200 OK
Content-Type: image/jpg
'''
# Header与Body分隔符
LineSeparator = '\r\n\r\n'
# HTTP响应
HttpResponseBody = ''
# HTTP响应Bytes
HttpResponse = ''.encode(encoding='utf-8')
# 创建基于IPV4 TCP的socket
ServerSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# socket的绑定与监听
ServerSocket.bind(HttpHost)
ServerSocket.listen(100)
print("server is running at http://localhost:%d" % HttpPort)
# 获取HTTP请求报头
def get_headers(request):
# 分隔符切割
headers_array = request.split('\r\n')
# HTTP请求头首行 是请求方法 请求路径 HTTP协议版本
headers = {}
for header_item in headers_array[1:]:
item_ = header_item.split(': ')
headers[item_[0]] = item_[1]
return headers
# 获取URL参数
def get_get_args(request_url):
get_args_arr = request_url[request_url.find('?') + 1:].split('&')
# print(get_args_arr)
get_args = {}
try:
for get_item in get_args_arr:
item_ = get_item.split('=')
get_args[item_[0]] = item_[1]
return get_args
except:
return {}
# 获取POST表单参数
def get_post_args(request_body):
post_args_arr = request_body.split('&')
post_args = {}
for post_item in post_args_arr:
item_ = post_item.split('=')
post_args[item_[0]] = item_[1]
return post_args
# 处理客户端连接
while True:
Client, Address = ServerSocket.accept()
# 客户端的HTTP请求
Request = Client.recv(1024).decode(encoding='utf-8')
# 将请求分为报头与主体
RequestText = Request.split(LineSeparator)
# 报头
RequestHeader = RequestText[0]
# 主体
RequestBody = RequestText[1]
# 请求方法
RequestMethod = RequestHeader.split(' ')[0]
# 请求路径
RequestUrl = RequestHeader.split(' ')[1].split('?')[0]
# 请求头部
RequestHeaders = get_headers(RequestHeader)
# 响应清空
HttpResponseBody = ''
HttpResponse = ''.encode(encoding='utf-8')
# GET方法的处理
if RequestMethod == 'GET':
# 请求的根目录
if RequestUrl[-1] == '/':
RequestUrl += 'index.html'
print(RequestUrl)
# 网页
if RequestUrl.split('.')[-1] == 'html':
try:
# 打开并读取html文件
res = open('.' + RequestUrl, 'rb')
StaticHtml = res.read()
HttpResponse += HttpHtmlResponseHeader.encode(encoding='utf-8')
HttpResponse += (HttpResponseBody.encode(encoding='utf-8') + StaticHtml)
res.close()
except Exception:
HttpResponse += HttpHtmlResponseHeader.encode(encoding='utf-8')
HttpResponse += '<html><br>ERROR !<br></html>'.encode(encoding='utf-8')
# 图片
elif RequestUrl.split('.')[-1] == 'jpg':
try:
res = open('.' + RequestUrl, 'rb')
ImageFile = res.read()
HttpResponse += HttpImageResponseHeader.encode(encoding='utf-8')
HttpResponse += (HttpResponseBody.encode(encoding='utf-8') + ImageFile)
res.close()
except Exception:
HttpResponse += HttpImageResponseHeader.encode(encoding='utf-8')
HttpResponse += ''.encode(encoding='utf-8')
HttpResponse += '<br><br>The next is post test <br>'.encode(encoding='utf-8')
HttpResponse += '''
<form action="/" method="post">
<p>Text1: <input type="text" name="Text1" /></p>
<p>Text2: <input type="text" name="Text2" /></p>
<input type="submit" value="Submit" />
</form>
'''.encode(encoding='utf-8')
# POST方法的处理
elif RequestMethod == 'POST':
HttpResponseBody += '''<html>
Your method is POST and your request url is ''' + RequestUrl + '''
<br>
The following are you headers :<br>
'''
for item in RequestHeaders.items():
HttpResponseBody += ('key :' + item[0] + ",value :" + item[1] + '<br>')
HttpResponseBody += 'This is your form :<br>'
PostArgs = get_post_args(RequestBody)
for item in PostArgs.items():
HttpResponseBody += ('key :' + item[0] + ",value :" + item[1] + '<br>')
HttpResponseBody += '''
<br>Try to get test <br>
<a href="http://''' + RequestHeaders['Host'] + '''/">get test</a>
</html>
'''
HttpResponse += (HttpHtmlResponseHeader + HttpResponseBody).encode(encoding='utf-8')
# 暂未支持的请求方法
else:
HttpResponseBody += '''
<html> So sorry
''' + RequestMethod + '''
method is not support :( <br>
</html>
'''
HttpResponse += (HttpHtmlResponseHeader + HttpResponseBody).encode(encoding='utf-8')
Client.sendall(HttpResponse)
Client.close()
5、我们执行 python3 server.py
,并打开浏览器输入之前的地址,可以看到以下输出:
我们点击pages,可以看到我们的图片也加载出来啦!
不错,我们的服务器工作良好!
6
总结
我们从0实现了一个简单的Web服务器(HTTP服务器),从最开始的只能显示一段文本到解析用户提交的参数与表单,并实现了图片的显示与静态网页文件的加载,有没有感到成就感满满呢?不过我们的服务器还是相当的简陋,期待大家按照自己的想法去对HTTP的其他方法进行实现与扩展:)
自知才疏学浅,如果文章中有错误还请各位读者斧正,如果您觉得本文内容对您有所帮助,可以考虑请我吃包辣条~
写的不错?赞赏一下
长按扫码赞赏我
作者:一野
声明:本文为 脚本之家专栏作者 投稿,未经允许请勿转载。
-END-
●
返回 上一级 搜索“Java 女程序员 大数据 留言送书 运维 算法 Chrome 黑客 Python JavaScript 人工智能 女朋友 MySQL 书籍 等关键词获取相关文章推荐。